# Copyright 2014 Sean Burford <vk2ete@vk2ete.net>.  All Rights Reserved.
import datetime
import logging
import re
import time
import hashlib

DEFAULT_TIMEZONE = 'Australia/NSW'


class LocationClue(object):

  def __init__(self, callsign, keyword, location):
    self.callsign = callsign.upper()
    self.keyword = keyword.upper()
    self.location = location


def _SetCallsign(evt, callsign):
  evt.callsign = callsign.upper()
  if evt.callsign.startswith('VK0'):
    # Antarctica
    pass
  elif evt.callsign.startswith('VK1'):
    evt.timezone = 'Australia/ACT'
  elif evt.callsign.startswith('VK2'):
    evt.timezone = 'Australia/NSW'
  elif evt.callsign.startswith('VK3'):
    evt.timezone = 'Australia/Victoria'
  elif evt.callsign.startswith('VK4'):
    evt.timezone = 'Australia/Queensland'
  elif evt.callsign.startswith('VK5'):
    evt.timezone = 'Australia/South'
  elif evt.callsign.startswith('VK6'):
    evt.timezone = 'Australia/West'
  elif evt.callsign.startswith('VK7'):
    evt.timezone = 'Australia/Tasmania'
  elif evt.callsign.startswith('VK8'):
    evt.timezone = 'Australia/North'
  elif evt.callsign.startswith('VK9'):
    # External territories
    pass


def GuessLocation(evt, location_clues):
  patch_count = 0
  text = '%s %s' % (evt.description, evt.subject)
  text = text.upper()

  for clue in location_clues:
    if evt.callsign and (evt.callsign.upper() != clue.callsign):
      continue
    if clue.keyword not in text:
      continue

    evt.location = clue.location
    if not evt.callsign:
      _SetCallsign(evt, clue.callsign)
      patch_count += 1
    patch_count += 1
    break

  return patch_count


class CalendarEvent(object):

  def __init__(self, src, subject, date_start, date_end):
    self.src = src
    self.timezone = None
    self.callsign = None

    self.subject = subject  # SUMMARY
    self.date_start = date_start  # DTSTART
    self.date_end = date_end  # DTEND
    self.description = ''  # DESCRIPTION
    self.comment = ''  # COMMENT
    self.private = False  # CLASS
    self.categories = set()
    self.location = None

  def _ToString(self, label, date_val):
    if not date_val:
      logging.warning('could not identify %s for an event', label)
      return label, ''

    timezone = self.timezone or DEFAULT_TIMEZONE

    if date_val.hour == 0 and date_val.minute == 0:
      year = date_val.strftime('%Y%m%d')
      return '%s;VALUE=DATE' % (label), year
    else:
      time_str = date_val.strftime('%Y%m%dT%H%M00')
      return '%s;TZID=/%s' % (label, timezone), time_str

  def _EncodeText(self, label, text):
    # RFC5545 3.3.11 text escaping.
    text = re.sub(r'([\,;])', r'\\\1', text)
    text = re.sub(r' & ', r' and ', text)
    return label, text

  def SetSubject(self, subject):
    assert not self.subject, 'Subject changed'
    self.subject = subject

  def AddCategory(self, category):
    # http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
    category = category.upper()
    if category.startswith('VK'):
      _SetCallsign(self, category.split()[0])
    else:
      self.categories.add(category)

  def SetTime(self, times):
    times = [x.strip() for x in times.split('-') if x]

    if len(times) not in (1, 2):
      logging.warning('%r lists %d times: %r', self.subject, len(times), times)
      return

    if len(times) >= 1:
      parsed = time.strptime(times[0], '%H:%M')
      self.date_start = self.date_start.replace(hour=parsed.tm_hour,
                                                minute=parsed.tm_min)
      self.date_end = self.date_end.replace(hour=parsed.tm_hour,
                                            minute=parsed.tm_min)

    if len(times) >= 2:
      parsed = time.strptime(times[1], '%H:%M')
      self.date_end = self.date_end.replace(hour=parsed.tm_hour,
                                            minute=parsed.tm_min)

  def SetDescription(self, desc):
    assert not self.description, 'Description changed'
    self.description = desc

  def SetComment(self, desc):
    assert not self.comment, 'Comment changed'
    self.comment = desc

  def UID(self):
    _, dtstart = self._ToString('DTSTART', self.date_start)
    _, dtend = self._ToString('DTEND', self.date_end)
    clean_subject = re.sub(r'[^\w]', '', self.subject).lower()
    try:
      src = self.src.split('/')[2]
    except IndexError:
      src = self.src.split('/')[-1]
    uid = '%s@%s' % (hashlib.sha1(dtstart + dtend + clean_subject).hexdigest(),
                     src)
    return uid

  def AsICalendar(self):
    # iCalendar format is described in RFC5545.
    # Validate results against http://icalvalid.cloudapp.net/
    if not self.subject:
      return

    ct = ','.join(sorted(self.categories))
    fields = {
        'CLASS': self.private and 'PRIVATE' or 'PUBLIC',
        'CATEGORIES': ct,
    }

    summary = self.subject
    summary_intro = []
    if self.callsign:
      summary_intro.append(self.callsign)
    if self.categories:
      summary_intro.append(ct)
    if summary_intro:
      summary = '[%s] %s' % (' '.join(summary_intro), self.subject)

    name, val = self._EncodeText('SUMMARY', summary)
    fields[name] = val

    if self.src.lower().startswith('http'):
      fields['URL;VALUE=URI'] = self.src

    for fname in ('description', 'comment', 'location'):
      val = getattr(self, fname)
      if val:
        name, enc = self._EncodeText(fname.upper(), val)
        fields[name] = enc

    now_dt = datetime.datetime.now()
    label, tm = self._ToString('DTSTAMP', now_dt)
    fields[label] = tm
    label, dtstart = self._ToString('DTSTART', self.date_start)
    if not dtstart:
      logging.warning('%r has no start date', self.subject)
    fields[label] = dtstart
    label, dtend = self._ToString('DTEND', self.date_end)
    if not dtend:
      logging.warning('%r has no end date', self.subject)
    fields[label] = dtend

    uid = self.UID()
    fields['UID'] = uid

    events = '\r\n'.join(['%s:%s' % (k, v) for k, v in fields.iteritems()])
    return 'BEGIN:VEVENT\r\n%s\r\nEND:VEVENT' % events

